package net.web.belerofont.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.web.belerofont.annotation.Inject;
import net.web.belerofont.annotation.WebMethod;

public final class InjectionUtil {

	private InjectionUtil() {
	}
	
	public static Object getFieldInstance(Class field) throws InstantiationException, IllegalAccessException {
		if (!field.isPrimitive()) {
			if (Character.class.getCanonicalName().equals(field.getCanonicalName())) {
				return Character.valueOf("".charAt(0));
			} else if (String.class.getCanonicalName().equals(field.getCanonicalName())) {
				return "";
			} else if (Float.class.getCanonicalName().equals(field.getCanonicalName())) {
				return Float.valueOf(0);
			} else if (Double.class.getCanonicalName().equals(field.getCanonicalName())) {
				return Double.valueOf(0);
			} else if (Long.class.getCanonicalName().equals(field.getCanonicalName())) {
				return Long.valueOf(0);
			} else if (Integer.class.getCanonicalName().equals(field.getCanonicalName())) {
				return Integer.valueOf(0);
			} else if (Byte.class.getCanonicalName().equals(field.getCanonicalName())) {
				return Byte.valueOf((byte)0);
			} else if (Date.class.getCanonicalName().equals(field.getCanonicalName())) {
				return new Date();
			} else {
				return field.newInstance();
			}
			
		} else {

		}
		return null;
	}
	
	public static void inject(String parent, Object owner, Field field, Map<String, String> values) throws InstantiationException, IllegalAccessException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException, ParseException {
		Object fieldInstance = field.getType().newInstance();

		String _methodName = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1); 
		Method _method = owner.getClass().getMethod(_methodName, field.getType());
		_method.invoke(owner, new Object[]{fieldInstance});
		
		String fieldName = field.getName();
		
		for(Field _field: field.getType().getDeclaredFields()) {
			String _fieldName = _field.getName();
			String valueName  = (parent.equals("")?"":parent + ".") + fieldName + "." + _fieldName;
			String _value     = values.get(valueName);
			
			
			String methodName = "set" + _field.getName().substring(0, 1).toUpperCase() + _field.getName().substring(1); 
			if (!_field.getType().isPrimitive()) {
				if (Character.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {

					Method method = fieldInstance.getClass().getMethod(methodName, Character.class);
					
					method.invoke(fieldInstance, new Object[]{_value.charAt(0)});
				} else if (String.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
					
					Method method = fieldInstance.getClass().getMethod(methodName, String.class);
					
					method.invoke(fieldInstance, new Object[]{_value});
				} else if (Float.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
					
					Method method = fieldInstance.getClass().getMethod(methodName, Float.class);
					
					method.invoke(fieldInstance, new Object[]{Float.valueOf(_value)});
				} else if (Double.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {

					Method method = fieldInstance.getClass().getMethod(methodName, Double.class);
					
					method.invoke(fieldInstance, new Object[]{Double.valueOf(_value)});
				} else if (Long.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
					
					Method method = fieldInstance.getClass().getMethod(methodName, Long.class);
					
					method.invoke(fieldInstance, new Object[]{Long.valueOf(_value)});
				} else if (Integer.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
					
					Method method = fieldInstance.getClass().getMethod(methodName, Integer.class);
					
					method.invoke(fieldInstance, new Object[]{Integer.valueOf(_value)});
				} else if (Byte.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
					
					Method method = fieldInstance.getClass().getMethod(methodName, Byte.class);
					
					method.invoke(fieldInstance, new Object[]{Byte.valueOf(_value)});
				} else if (Date.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
					SimpleDateFormat simple = new SimpleDateFormat("dd/MM/yyyy");
					
					Method method = fieldInstance.getClass().getMethod(methodName, Date.class);
					
					method.invoke(fieldInstance, new Object[]{simple.parse(_value)});
				} else {
					inject((parent.equals("")?"":parent + ".") + fieldName, fieldInstance,_field,values);
				}
			}
		}
	}

	
	public static void injectParam(String parent, Object owner, Map<String, String> values) throws InstantiationException, IllegalAccessException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException, ParseException {
		
		for(Field _field: owner.getClass().getDeclaredFields()) {
			String _fieldName = _field.getName();
			String valueName  = (parent.equals("")?"":parent + ".")  + _fieldName;
			String _value     = values.get(valueName);
			
			String methodName = "set" + _field.getName().substring(0, 1).toUpperCase() + _field.getName().substring(1); 
			if (_value != null) {
				if (!_field.getType().isPrimitive() ) {
					if (Character.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
	
						Method method = owner.getClass().getMethod(methodName, Character.class);
						
						method.invoke(owner, new Object[]{_value.charAt(0)});
					} else if (String.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
						
						Method method = owner.getClass().getMethod(methodName, String.class);
						
						method.invoke(owner, new Object[]{_value});
					} else if (Float.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
						
						Method method = owner.getClass().getMethod(methodName, Float.class);
						
						method.invoke(owner, new Object[]{Float.valueOf(_value)});
					} else if (Double.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
						
						Method method = owner.getClass().getMethod(methodName, Double.class);
						
						method.invoke(owner, new Object[]{Double.valueOf(_value)});
					} else if (Long.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
						
						Method method = owner.getClass().getMethod(methodName, Long.class);
						
						method.invoke(owner, new Object[]{Long.valueOf(_value)});
					} else if (Integer.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
						
						Method method = owner.getClass().getMethod(methodName, Integer.class);
						
						method.invoke(owner, new Object[]{Integer.valueOf(_value)});
					} else if (Byte.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
						
						Method method = owner.getClass().getMethod(methodName, Byte.class);
						
						method.invoke(owner, new Object[]{Byte.valueOf(_value)});
					} else if (Date.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
	
						SimpleDateFormat simple = new SimpleDateFormat("dd/MM/yyyy");
						
						Method method = owner.getClass().getMethod(methodName, Date.class);
						
						method.invoke(owner, new Object[]{simple.parse(_value)});
					} else {
						
						Method method = owner.getClass().getMethod(methodName, _field.getType());
						
						Object _fieldInstance = getFieldInstance(_field.getType());
						
						method.invoke(owner, new Object[]{_fieldInstance});

						injectParam((parent.equals("")?"":parent + ".") + _fieldName,_fieldInstance,values);
					}
				} 
			} else {
				if (!_field.getType().isPrimitive() ) {
					if (Character.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
						
						Method method = owner.getClass().getMethod(methodName, Character.class);
						
						method.invoke(owner, new Object[]{"".charAt(0)});
					} else if (String.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
						
						Method method = owner.getClass().getMethod(methodName, String.class);
						
						method.invoke(owner, new Object[]{""});
					} else if (Float.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
						
						Method method = owner.getClass().getMethod(methodName, Float.class);
						
						method.invoke(owner, new Object[]{Float.valueOf("0")});
					} else if (Double.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
						
						Method method = owner.getClass().getMethod(methodName, Double.class);
						
						method.invoke(owner, new Object[]{Double.valueOf("0")});
					} else if (Long.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
						
						Method method = owner.getClass().getMethod(methodName, Long.class);
						
						method.invoke(owner, new Object[]{Long.valueOf("0")});
					} else if (Integer.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
						
						Method method = owner.getClass().getMethod(methodName, Integer.class);
						
						method.invoke(owner, new Object[]{Integer.valueOf("0")});
					} else if (Byte.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
						
						Method method = owner.getClass().getMethod(methodName, Byte.class);
						
						method.invoke(owner, new Object[]{Byte.valueOf("0")});
					} else if (Date.class.getCanonicalName().equals(_field.getType().getCanonicalName())) {
						
						Method method = owner.getClass().getMethod(methodName, Date.class);
						
						method.invoke(owner, new Object[]{new Date()});
					} else {
						
						Method method = owner.getClass().getMethod(methodName, _field.getType());
						
						Object _fieldInstance = getFieldInstance(_field.getType());
						
						method.invoke(owner, new Object[]{_fieldInstance});

						injectParam((parent.equals("")?"":parent + ".") + _fieldName,_fieldInstance,values);
					}
				}
			}
		}
	}
	
	
	public static Object  executeWebMethod(Object owner, Map<String, String> values) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, InstantiationException, SecurityException, NoSuchMethodException, ParseException {
		for(Method method : owner.getClass().getDeclaredMethods()) {
			for (Annotation anotacao : method.getAnnotations()) {
				List<Object> params = new LinkedList<Object>();
				if (anotacao instanceof WebMethod && ((WebMethod)anotacao).defaultMethod().equalsIgnoreCase("true")) {
					for (Class param : method.getParameterTypes()){
						
						Object paramValue = param.newInstance(); 
						String paramName = param.getSimpleName();
						paramName = paramName.substring(0,1).toLowerCase() + paramName.substring(1);
						InjectionUtil.injectParam(paramName, paramValue,values);
						params.add(paramValue);
					
					}
					
					return method.invoke(owner, params.toArray());
				}
			}
		}
		return null;
	}
	
	
	public static Object executeWebMethod(Object owner, String methodName, Map<String, String> values) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, InstantiationException, SecurityException, NoSuchMethodException, ParseException {
		for(Method method : owner.getClass().getDeclaredMethods()) {
			if (method.getName().equals(methodName)) {
				for (Annotation anotacao : method.getAnnotations()) {
					List<Object> params = new LinkedList<Object>();
					if (anotacao instanceof WebMethod) {
						for (Class param : method.getParameterTypes()){
							
							Object paramValue = param.newInstance(); 
							String paramName = param.getSimpleName();
							paramName = paramName.substring(0,1).toLowerCase() + paramName.substring(1);
							InjectionUtil.injectParam(paramName, paramValue,values);
							params.add(paramValue);
						
						}
						
						return method.invoke(owner, params.toArray());
					}
				}
			}
		}
		
		return null;
	}
	
	
	public static void injectValuesInField(Object owner, Map<String, String> values) throws SecurityException, IllegalArgumentException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, ParseException {
		for(Field field : owner.getClass().getDeclaredFields()) {
			for (Annotation anotacao : field.getAnnotations()) {
				if (anotacao instanceof Inject) {
					InjectionUtil.inject("", owner,field,values);
				}
			}
		}

	}
	
}
